package msc.ai.crypto.jade;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.HashMap;

import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.Behaviour;
import jade.core.behaviours.CyclicBehaviour;
import jade.core.behaviours.OneShotBehaviour;
import jade.core.behaviours.TickerBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.lang.acl.UnreadableException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SealedObject;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.DESedeKeySpec;

import org.apache.commons.codec.binary.Base64;

// FIXED AGAIN!!!
//import sun.misc.BASE64Encoder;

import msc.ai.crypto.jade.security.KDC;
//import sun.misc.BASE64Encoder;
import msc.ai.crypto.jade.security.keymanager.AgentPublicKeyDetails;

public class PaymentGatewayAgent extends Agent
{
	/**
	 * 
	 */
	private static final long serialVersionUID = -6626818547400938133L;
	
	//private HashMap<String, CreditCard> ccList;
	private HashMap<String, SealedObject> ccListEncrypt=null;
	private PaymentGatewayGui pgGui;

	private PasswordPanel pwGui;
	private SecretKey key; // add by Saneth

	private String dataStorage="./DataStorage/";
	
	private AgentPublicKeyDetails myKeyMgrKeys;
	private ArrayList<AID> kyMgrList = new ArrayList<AID>();	

	protected void setup() 
	{
		
		

		pwGui = new PasswordPanel(this);
		pwGui.showGui();

		// Load stored encrypted (SealedObject) HashMap object
		try {
			File file = new File(dataStorage+this.getLocalName() + ".cred");
			boolean exists = file.exists();
			if (exists) {
				FileInputStream f_in = new FileInputStream(dataStorage+this.getLocalName()
						+ ".cred");
				ObjectInputStream obj_in = new ObjectInputStream(f_in);
				Object obj = obj_in.readObject();
				if (obj instanceof HashMap) {
					ccListEncrypt = (HashMap) obj;
				}

			}

		} catch (Exception e) {
			e.printStackTrace();
		}

		// Create new hash map if no stored object
		if(ccListEncrypt==null){
		//ccList = new HashMap<String, CreditCard>(); old version
		ccListEncrypt = new HashMap<String, SealedObject>();// add by Saneth
		}
		
/*		pgGui = new PaymentGatewayGui(this);   old version
		pgGui.showGui();*/
		
		System.out.println(getAID().getLocalName() + ": Payment Gateway Started");
		DFAgentDescription dfd = new DFAgentDescription();
		dfd.setName(getAID());
		ServiceDescription sd = new ServiceDescription();
		sd.setType("payment-gateway");
		sd.setName("credit-card-authorization");
		dfd.addServices(sd);
		
		try 
		{
			DFService.register(this, dfd);
		}
		catch (FIPAException fe) 
		{
			fe.printStackTrace();
		}	
		
		addBehaviour(new CCAuthorizationBehavior());
		
		// Find Key Managers
		addBehaviour(new TickerBehaviour(this, 10000) 
			{
				protected void onTick() 
				{	
						DFAgentDescription template = new DFAgentDescription();
						ServiceDescription sd = new ServiceDescription();
						sd.setType("key-manager");
						template.addServices(sd);
						try 
						{
							DFAgentDescription[] result = DFService.search(myAgent, template);
							boolean isMyKeyMgrAlive = false;
							
							kyMgrList.clear();
							for (int i = 0; i < result.length; ++i) 
							{
								kyMgrList.add(result[i].getName());
								
								if (myKeyMgrKeys != null && (result[i].getName().equals(myKeyMgrKeys.getKeyMgrAID())))
								{
									isMyKeyMgrAlive = true;
								}
								
							}
							
							// need to register ?
							if (myKeyMgrKeys == null)
							{
								// Discard old shared keys with old key manager
								myAgent.clearSharedKeyStore();
								myAgent.addBehaviour(new KeyManagerRegisterBehavior());
							}
							else if (isMyKeyMgrAlive == false)
							{
								// Old Key Manager is dead. Register with a new key manger
								// Discard old shared keys with old key manager
								myAgent.clearSharedKeyStore();							
								myAgent.addBehaviour(new KeyManagerRegisterBehavior());
							}			
							
						}
						catch (FIPAException fe) 
						{
							fe.printStackTrace();
						}
					
				}
			} 
		);
		
		
	}
	
	
	public void setPgGui(PaymentGatewayGui pgGui) {
		this.pgGui = pgGui;
	}


	public void setKey(SecretKey key) {
		this.key = key;
	}

	
	protected void takeDown() 
	{
		
		try 
		{
			DFService.deregister(this);
		}
		catch (FIPAException fe) 
		{
			fe.printStackTrace();
		}
		
		if (this.pgGui !=null) this.pgGui.dispose();
		
		System.out.println("Payment-Gateway "+getAID().getName()+" terminating.");
	}
	
/*	public  HashMap<String, CreditCard> getCreditCardList()  old version
	{
		return this.ccList;
	}*/
	
	public  HashMap<String, SealedObject> getCreditCardList()
	{
		return this.ccListEncrypt;
	}
	
	public void addCreditCard(final CreditCard cc,final String CCIdentifier)
	{
		addBehaviour(new OneShotBehaviour() 
		{			
		
			@Override
			public void action()
			{
				System.out.println( getAID().getLocalName() + ": " + cc.getCreditCardNo()+ " card added."  );
				
/*				if (ccList == null ) ccList =  new HashMap<String, CreditCard>();  old version
				
				ccList.put(cc.getCreditCardNo(),cc);*/
				
				if (ccListEncrypt == null ) ccListEncrypt =  new HashMap<String, SealedObject>();
				
				try {
					//Adding encrypted CreditCard Object and  encoded Credit Card  number as key 
					ccListEncrypt.put(CCIdentifier,getEncryptCreditCard(cc));
					System.out.println("ADDING : "+CCIdentifier);
					FileOutputStream f_out = new 
						FileOutputStream(dataStorage+getAID().getLocalName()+".cred");

					ObjectOutputStream obj_out = new
						ObjectOutputStream (f_out);

					obj_out.writeObject ( ccListEncrypt );
					
					obj_out.close();
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}finally{
					
				}
				

				
			}
			
		});
		
	}
	
	
	private class CCAuthorizationBehavior extends CyclicBehaviour
	{

		@Override
		public void action()
		{
			MessageTemplate mt = MessageTemplate.MatchPerformative(ACLMessage.REQUEST);
			ACLMessage msg = myAgent.receive(mt, KDC.getKey());
			if (msg != null) 
			{
				// someone looking for authorization.
				
				System.out.println(getAID().getLocalName() + ": CC Authorization Process Started");
				try
				{
					BuyingItem bItem = (BuyingItem) msg.getContentObject();
					System.out.println(getAID().getLocalName() + ": Authorizing... " + bItem.getCreditCardNo() 
										+ ", " + bItem.getCreditCVVNo() + ", " + bItem.getCreditCardType() );
					
					ACLMessage reply = msg.createReply();
					
					
					if (bItem != null)
					{
						//CreditCard cc  = ccList.get(bItem.getCreditCardNo()); //old version
						CreditCard cc=null;
						try {
							
							SealedObject sobject =ccListEncrypt.get(getEncrypteCCIdentifier(bItem.getCreditCardNo()));
							if(sobject!=null){
							cc = getDecryptCreditCard(sobject);
							}
						} catch (Exception e) {
							
							e.printStackTrace();
						}
						
						
						if (cc != null)
						{
							try{
							if (cc.validateCCInfo(bItem.getCreditCardNo(), bItem.getCreditCVVNo(), bItem.getCreditCardType()))
							{
								int authCode = (int) (Math.random()*100000);
								reply.setPerformative(ACLMessage.INFORM);
								reply.setContent("credit-card-atuhorized:" + authCode);
								System.out.println(" iiiiiiii  credit-card-atuhorized:" + authCode);
							}
							else
							{
								reply.setPerformative(ACLMessage.FAILURE);
								reply.setContent("credit-card-not-valid");
							}
							}catch (Exception e) {
								reply.setPerformative(ACLMessage.FAILURE);
								reply.setContent("credit-card-not-valid");
								e.printStackTrace();;
							}
							
							
						}
						else
						{
							reply.setPerformative(ACLMessage.FAILURE);
							reply.setContent("invalid-credit_card");
						}
						
					}
					else
					{
						reply.setPerformative(ACLMessage.FAILURE);
						reply.setContent("invalid-purchase-det");
					}
					
					myAgent.send(reply, KDC.getKey());
					System.out.println(getAID().getLocalName() + ": Reply Sent");
				} 
				catch (UnreadableException e)
				{
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			else
			{
				block();
			}
			
		}
		
	}
	
	
	private class KeyManagerRegisterBehavior extends Behaviour
	{

		/**
		 * 
		 */
		private static final long serialVersionUID = -67584494012841654L;
		
		private int registerStep = 0;		
		private MessageTemplate mt;
		
		@Override
		public void action()
		{
			switch (registerStep)
			{
				case 0:	
					// Register with Key Manager
					System.out.println(getAID().getLocalName() + ": Key Manager Registration started");
					ACLMessage req = new ACLMessage(ACLMessage.REQUEST);
					for (int i = 0; i < kyMgrList.size(); ++i) 
					{
						//TODO: Select only one???
						req.addReceiver((AID)kyMgrList.get(i));
					}
					
					req.setConversationId("keymgr_register");
					req.setReplyWith("register_"+System.currentTimeMillis()); // Unique value
//					req.setSecureMsg(false);
					myAgent.send(req); // send unsecured message
			          
					mt = MessageTemplate.and(
							MessageTemplate.MatchPerformative(ACLMessage.INFORM),
							MessageTemplate.and(
									MessageTemplate.MatchConversationId("keymgr_register_done"),
									MessageTemplate.MatchInReplyTo(req.getReplyWith())
							)
						);
										
					registerStep = 1;
					break;
					
				case 1:
					// Receive Key Details
					ACLMessage keyMgrReply = myAgent.receive(mt); // this is unsecure
					if (keyMgrReply != null) 
					{
						System.out.println(getAID().getLocalName() + ": Key Manager Regisration Done"); 
						try
						{
							AgentPublicKeyDetails pkDetails = (AgentPublicKeyDetails) keyMgrReply.getContentObject();							
							myKeyMgrKeys = pkDetails;
							
							System.out.println(getAID().getLocalName() + ": Key Manager " + myKeyMgrKeys.getKeyMgrAID().getLocalName());
							registerStep = 2;
							
						} catch (UnreadableException e)
						{
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						
						
					}
					else
					{
						//TODO block(); ?
					}
					
										
					
					
					break;
				default:
					break;
			}
			
		}

		@Override
		public boolean done()
		{
			 
			if (registerStep == 2) 
			{
				return true;
			}
			return false;
		}
		
		
	} // End of KeyManagerRegisterBehavior
	
	// Encrypt CreditCard
	public SealedObject getEncryptCreditCard(CreditCard creditCard)
			throws Exception {

		Cipher cipher = Cipher.getInstance("AES");
		cipher.init(Cipher.ENCRYPT_MODE, key);
		SealedObject sealedObject = new SealedObject(creditCard, cipher);
		return sealedObject;
	}

	// Decrypt CreditCard
	public CreditCard getDecryptCreditCard(SealedObject sealedObject)
			throws Exception {

		// Prepare the decrypter
		Cipher dcipher = Cipher.getInstance("AES");
		dcipher.init(Cipher.DECRYPT_MODE, key);

		// Unseal (decrypt) the class
		CreditCard creditCard = (CreditCard) sealedObject.getObject(dcipher);
		return creditCard;

	}


	// Generate unique encoded identifier for credit card number
	  public  String getEncrypteCCIdentifier( String source ) throws Exception
	  {
		
	        String hashValue = null;
	       
	        	MessageDigest msgDigest = MessageDigest.getInstance("SHA-1");
	            msgDigest.update(source.getBytes("UTF-16"));
	            byte rawByte[] = msgDigest.digest();
	          //  hashValue = (new BASE64Encoder()).encode(rawByte);
	           hashValue = new String( Base64.encodeBase64(rawByte));
	            
		  
		  
		  
	     return hashValue;
	    
	  }


}
